package ch.zhaw.cctd.gui;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.Timer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.zhaw.cctd.domain.DomainFacade;
import ch.zhaw.cctd.domain.GraphicalElement;
import ch.zhaw.cctd.domain.player.RessourceException;
import ch.zhaw.cctd.domain.prototype.TowerPrototype;
import ch.zhaw.cctd.domain.tower.TowerBuildException;

/**
 * Verwaltet das Spielfeld und wartet auf Benutzereingaben.
 * 
 * @author Lukas Spirig
 * 
 */
public class FieldGUI extends JLayeredPane implements ActionListener, MouseListener, KeyListener, MouseMotionListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = 31133243055765672L;
	private static final Logger logger = LoggerFactory.getLogger(FieldGUI.class);
	private JViewport viewport;
	private TowerPanel towerRect = new TowerPanel();
	private MatchGUI matchgui;
	private Timer timer;
	private long time = 0;
	private Point mousePos = new Point(-1, -1);
	private Point mouseDrag = null;

	private HashMap<UUID, ImagePanel> moveableList;
	private HashMap<UUID, ImagePanel> towerList;

	public final static int MOVE_PIXEL = 20;
	public final static int MAX_SCROLL = 150;
	public final static int INCREASE = 0; // direction
	public final static int DECREASE = 1; // direction
	public final static int X_AXIS = 0; // axis
	public final static int Y_AXIS = 1; // axis

	public FieldGUI(MatchGUI matchgui) {
		this.matchgui = matchgui;
		this.viewport = matchgui.getViewport();
		timer = new Timer(CCTDFrame.REFRESH_RATE, this);
		Dimension size = DomainFacade.getMapSize();
		logger.trace("FieldGui Size: {}", size.toString());
		setSize(size);
		setPreferredSize(size);

		addMouseMotionListener(this);
		addMouseListener(this);
		setFocusable(true);
		addKeyListener(this);

		InputMap inputMapL = this.getInputMap();
		ActionMap actionMapL = this.getActionMap();
		InputMap inputMap = viewport.getInputMap();
		ActionMap actionMap = viewport.getActionMap();

		Action upKeyAction = new MoveAction(viewport, DECREASE, Y_AXIS);
		KeyStroke upKey = KeyStroke.getKeyStroke("UP");
		inputMap.put(upKey, "up");
		actionMap.put("up", upKeyAction);
		inputMapL.put(upKey, "up");
		actionMapL.put("up", upKeyAction);

		Action downKeyAction = new MoveAction(viewport, INCREASE, Y_AXIS);
		KeyStroke downKey = KeyStroke.getKeyStroke("DOWN");
		inputMap.put(downKey, "down");
		actionMap.put("down", downKeyAction);
		inputMapL.put(downKey, "down");
		actionMapL.put("down", downKeyAction);

		Action leftKeyAction = new MoveAction(viewport, DECREASE, X_AXIS);
		KeyStroke leftKey = KeyStroke.getKeyStroke("LEFT");
		inputMap.put(leftKey, "left");
		actionMap.put("left", leftKeyAction);
		inputMapL.put(leftKey, "left");
		actionMapL.put("left", leftKeyAction);

		Action rightKeyAction = new MoveAction(viewport, INCREASE, X_AXIS);
		KeyStroke rightKey = KeyStroke.getKeyStroke("RIGHT");
		inputMap.put(rightKey, "right");
		actionMap.put("right", rightKeyAction);
		inputMapL.put(rightKey, "right");
		actionMapL.put("right", rightKeyAction);

		moveableList = new HashMap<UUID, ImagePanel>(300);
		towerList = new HashMap<UUID, ImagePanel>();

		List<? extends GraphicalElement> staticMapObjects = null;
		staticMapObjects = DomainFacade.getStaticMapObjects();

		for (GraphicalElement ge : staticMapObjects) {
			ImagePanel panel = getLabelImage(ge);

			logger.trace("Move " + ge.getTexture() + " level " + ge.getLayer() + " location " + ge.getLocation() + " width " + panel.getWidth() + " height "
					+ panel.getHeight());
			add(panel, JLayeredPane.DEFAULT_LAYER, ge.getLayer());
		}
		add(towerRect, JLayeredPane.MODAL_LAYER, 0);
	}

	public void runGame() {
		logger.trace("run game Timer");
		timer.start();
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		if (mouseDrag != null) {
			Point pos = viewport.getViewPosition();
			int dx = 0, dy = 0;
			int maxWidth = getWidth() - viewport.getWidth();
			int maxHeight = getHeight() - viewport.getHeight();

			if (e.getX() - pos.getX() < mouseDrag.getX())
				dx -= MOVE_PIXEL;
			else if (e.getX() - pos.getX() > mouseDrag.getX())
				dx += MOVE_PIXEL;
			if (e.getY() - pos.getY() < mouseDrag.getY())
				dy -= MOVE_PIXEL;
			else if (e.getY() - pos.getY() > mouseDrag.getY())
				dy += MOVE_PIXEL;

			if (pos.getX() + dx < 0)
				dx = -(int) pos.getX();
			if (pos.getX() + dx > maxWidth)
				dx = maxWidth - (int) pos.getX();
			if (pos.getY() + dy < 0)
				dy = -(int) pos.getY();
			if (pos.getY() + dy > maxHeight)
				dy = maxHeight - (int) pos.getY();
			pos.translate(dx, dy);

			viewport.setViewPosition(pos);
		}
		if (towerRect.isVisible())
			towerRect.setLocation(e.getPoint());
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		mousePos = viewport.getMousePosition();
		if (towerRect.isVisible())
			towerRect.setLocation(e.getPoint());
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		if (towerRect.isVisible() && e.getButton() != MouseEvent.BUTTON1)
			towerRect.reset();
		else if (towerRect.isVisible() && e.getButton() == MouseEvent.BUTTON1) {
			try {
				logger.trace("Place Tower");
				towerRect.placeTower();
			} catch (RessourceException e1) {
				logger.trace("Kein Gold");
				matchgui.getChat().addSystemMessage("Zuwenig Gold");
				// matchgui.message("Zuwenig Gold",
				// "Sie haben nicht genug Gold um diesen Tower zu bauen!");
			} catch (TowerBuildException e1) {
				logger.trace("Sonst ungültig");
				matchgui.getChat().addSystemMessage("Ungültiger Bauplatz");
				
				// matchgui.message("Ungültige Position",
				// "Der Tower kann hier nicht platziert werden!");
			}
		}
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		requestFocus();
	}

	@Override
	public void mouseExited(MouseEvent e) {
		logger.trace("Mouse exited");
		mousePos = new Point(-1, -1);
	}

	@Override
	public void mousePressed(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON3)
			mouseDrag = new Point(mousePos);
		if (towerRect.isVisible())
			towerRect.setLocation(e.getPoint());
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		mouseDrag = null;
	}

	@Override
	public void keyPressed(KeyEvent e) {
	}

	@Override
	public void keyReleased(KeyEvent e) {
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}

	public void stopThreads() {
		timer.stop();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource().equals(timer)) {
			// moveViewport();
			paintField();

			matchgui.setCntCreeps(Integer.toString(DomainFacade.getCreepCount()));
			matchgui.setCntWaves(Integer.toString(DomainFacade.getWaveNumber()));
			matchgui.setCntGold(Integer.toString(DomainFacade.getLocalPlayerMoney()));

			time += timer.getDelay();
			String sec = Long.toString((time / 1000) % 60);
			if (sec.length() < 2)
				sec = "0" + sec;
			String min = Long.toString(Math.round(Math.floor(time / 60000)));
			if (min.length() < 2)
				min = "0" + min;
			matchgui.setTime(min + ":" + sec);
		}
	}

	@SuppressWarnings("unchecked")
	private void paintField() {
		List<? extends GraphicalElement> towers = DomainFacade.getTowersMapObjects();

		if (towerList.size() > 0) {
			Set<UUID> keySet = ((HashMap<UUID, ImagePanel>) towerList.clone()).keySet();
			for (GraphicalElement tower : towers) {
				keySet.remove(tower.getUniqueId());
			}
			for (UUID tower : keySet) {
				logger.trace("Removing tower " + tower);
				remove(towerList.remove(tower));
			}
		}

		if (towers != null) {
			for (GraphicalElement tower : towers) {
				if (towerList.containsKey(tower.getUniqueId())) {
					// ImagePanel tmp = towerList.get(tower.getUniqueId());
				} else {
					ImagePanel tmp = getLabelImage(tower);
					if (logger.isDebugEnabled())
						logger.trace("Add " + tower.getTexture() + " level " + tower.getLayer() + " id " + tower.getUniqueId() + " location "
								+ tower.getLocation());
					towerList.put(tower.getUniqueId(), tmp);

					add(tmp, JLayeredPane.DEFAULT_LAYER, tower.getLayer());
				}
			}
		}

		List<? extends GraphicalElement> objects = DomainFacade.getMovableMapObjects();

		if (moveableList.size() > 0) {
			Set<UUID> keySet = ((HashMap<UUID, ImagePanel>) towerList.clone()).keySet();
			for (GraphicalElement tower : towers) {
				keySet.remove(tower.getUniqueId());
			}
			for (UUID object : keySet) {
				logger.trace("Removing object " + object);
				remove(moveableList.remove(object));
			}
		}

		if (objects != null) {
			for (GraphicalElement el : objects) {
				if (moveableList.containsKey(el.getUniqueId())) {
					ImagePanel tmp = moveableList.get(el.getUniqueId());
					logger.trace("Move " + el.getTexture() + " level " + el.getLayer() + " id " + el.getUniqueId() + " location " + el.getLocation());
					tmp.setLocation((int) el.getLocation().getX(), (int) el.getLocation().getY());

				} else {
					ImagePanel tmp = getLabelImage(el);
					logger.trace("Add " + el.getTexture() + " level " + el.getLayer() + " id " + el.getUniqueId() + " location " + el.getLocation());
					moveableList.put(el.getUniqueId(), tmp);

					add(tmp, JLayeredPane.PALETTE_LAYER, el.getLayer());
				}
			}
		}
	}

	public void placeTower(TowerPrototype tower) {
		towerRect.initTower(tower);
	}

	private ImagePanel getLabelImage(GraphicalElement el) {
		ImagePanel ip = new ImagePanel(el);

		return ip;
	}

	public class ImagePanel extends JPanel {

		/**
		 * 
		 */
		private static final long serialVersionUID = -8006081346848256361L;
		private BufferedImage image;

		public ImagePanel(GraphicalElement el) {
			URL imgUrl = FieldGUI.class.getResource(CCTDFrame.GRAPHICS_PATH + el.getTexture());
			try {
				image = ImageIO.read(imgUrl);
			} catch (IOException ex) {
			}
			setBounds(new Rectangle(el.getLocation(), el.getSize()));
		}

		@Override
		public void paint(Graphics g) {
			g.drawImage(image, 0, 0, (int) getSize().getWidth(), (int) getSize().getHeight(), null);
		}
	}

	public class TowerPanel extends JPanel {

		/**
		 * 
		 */
		private static final long serialVersionUID = 6064547351040886042L;
		private BufferedImage validImg;
		private BufferedImage invalidImg;
		private boolean valid = false;
		private TowerPrototype tower = null;

		public TowerPanel() {
			setVisible(false);
			URL validUrl = FieldGUI.class.getResource(CCTDFrame.GRAPHICS_PATH + "valid.png");
			URL invalidUrl = FieldGUI.class.getResource(CCTDFrame.GRAPHICS_PATH + "invalid.png");
			try {
				validImg = ImageIO.read(validUrl);
				invalidImg = ImageIO.read(invalidUrl);
			} catch (IOException ex) {
			}

			setOpaque(true);
		}

		@Override
		public void paint(Graphics g) {
			if (isVisible()) {
				if (valid)
					g.drawImage(validImg, 0, 0, getWidth(), getHeight(), null);
				else
					g.drawImage(invalidImg, 0, 0, getWidth(), getHeight(), null);
			}
		}

		public boolean isValid() {
			return valid;
		}

		@Override
		public void setLocation(Point p) {
			Point gridPoint = DomainFacade.isValidTowerBuildLocation(new Rectangle(p, this.getSize()));
			if (gridPoint == null) {
				valid = false;
				super.setLocation((int) p.getX() - getWidth() / 2, (int) p.getY() - getHeight() / 2);
			} else {
				valid = true;
				super.setLocation(gridPoint);
			}
		}

		public void initTower(TowerPrototype tower) {
			this.tower = tower;
			setSize(tower.getSize(), tower.getSize());
			setVisible(true);
		}

		public void placeTower() throws RessourceException, TowerBuildException {
			if (tower != null && isVisible() && valid) {
				DomainFacade.buildTower(tower, getLocation());
				reset();
			}
		}

		public void reset() {
			setVisible(false);
			tower = null;
			valid = false;
		}
	}

	public class MoveAction extends AbstractAction {
		/**
		 * 
		 */
		private static final long serialVersionUID = -3868591135567504920L;
		private JViewport viewport;
		private int direction;
		private int axis;

		public MoveAction(JViewport viewport, int direction, int axis) {
			if (viewport == null) {
				throw new IllegalArgumentException("null viewport not permitted");
			}
			this.viewport = viewport;
			this.direction = direction;
			this.axis = axis;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			int maxWidth = getWidth() - viewport.getWidth();
			int maxHeight = getHeight() - viewport.getHeight();

			int dx = 0;
			int dy = 0;

			if (axis == X_AXIS) {
				dx = MOVE_PIXEL;
			} else { // axis == Y_AXIS
				dy = MOVE_PIXEL;
			}

			if (direction == DECREASE) {
				dx = -dx;
				dy = -dy;
			}

			Point pos = viewport.getViewPosition();

			if (pos.getX() + dx < 0)
				dx = -(int) pos.getX();
			if (pos.getX() + dx > maxWidth)
				dx = maxWidth - (int) pos.getX();
			if (pos.getY() + dy < 0)
				dy = -(int) pos.getY();
			if (pos.getY() + dy > maxHeight)
				dy = maxHeight - (int) pos.getY();

			pos.translate(dx, dy);
			viewport.setViewPosition(pos);

			if (towerRect.isVisible())
				towerRect.setLocation(getMousePosition());
		}
	}
}
